package io.debezium.connector.postgresql;

import io.debezium.config.CommonConnectorConfig;
import io.debezium.config.ConfigDefinition;
import io.debezium.config.Configuration;
import io.debezium.config.EnumeratedValue;
import io.debezium.config.Field;
import io.debezium.config.Field.Group;
import io.debezium.connector.AbstractSourceInfo;
import io.debezium.connector.SourceInfoStructMaker;
import io.debezium.connector.postgresql.connection.GaussDBConnection;
import io.debezium.connector.postgresql.connection.MessageDecoder;
import io.debezium.connector.postgresql.connection.MessageDecoderContext;
import io.debezium.connector.postgresql.connection.pgoutput.PgOutputMessageDecoder;
import io.debezium.connector.postgresql.connection.pgproto.PgProtoMessageDecoder;
import io.debezium.connector.postgresql.connection.wal2json.NonStreamingWal2JsonMessageDecoder;
import io.debezium.connector.postgresql.connection.wal2json.StreamingWal2JsonMessageDecoder;
import io.debezium.connector.postgresql.snapshot.AlwaysSnapshotter;
import io.debezium.connector.postgresql.snapshot.InitialOnlySnapshotter;
import io.debezium.connector.postgresql.snapshot.InitialSnapshotter;
import io.debezium.connector.postgresql.snapshot.NeverSnapshotter;
import io.debezium.connector.postgresql.spi.Snapshotter;
import io.debezium.data.Envelope;
import io.debezium.data.Envelope.Operation;
import io.debezium.jdbc.JdbcConfiguration;
import io.debezium.relational.ColumnFilterMode;
import io.debezium.relational.RelationalDatabaseConnectorConfig;
import io.debezium.relational.TableId;
import io.debezium.relational.Tables;
import io.debezium.util.Strings;
import org.apache.kafka.common.config.ConfigDef;
import org.apache.kafka.common.config.ConfigDef.Importance;
import org.apache.kafka.common.config.ConfigDef.Type;
import org.apache.kafka.common.config.ConfigDef.Width;
import org.apache.kafka.common.config.ConfigValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.time.Duration;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

public class PostgresConnectorConfig extends RelationalDatabaseConnectorConfig {
    private static final Logger LOGGER = LoggerFactory.getLogger(PostgresConnectorConfig.class);
    protected static final String DATABASE_CONFIG_PREFIX = "database.";
    protected static final int DEFAULT_PORT = 5432;
    protected static final int DEFAULT_SNAPSHOT_FETCH_SIZE = 10240;
    protected static final int DEFAULT_MAX_RETRIES = 6;
    public static final Field PORT;
    public static final Field PLUGIN_NAME;
    public static final Field SLOT_NAME;
    public static final Field DROP_SLOT_ON_STOP;
    public static final Field PUBLICATION_NAME;
    public static final Field PUBLICATION_AUTOCREATE_MODE;
    public static final Field STREAM_PARAMS;
    public static final Field MAX_RETRIES;
    public static final Field RETRY_DELAY_MS;
    public static final Field ON_CONNECT_STATEMENTS;
    public static final Field SSL_MODE;
    public static final Field SSL_CLIENT_CERT;
    public static final Field SSL_CLIENT_KEY;
    public static final Field SSL_CLIENT_KEY_PASSWORD;
    public static final Field SSL_ROOT_CERT;
    public static final Field SSL_SOCKET_FACTORY;
    public static final Field SNAPSHOT_MODE;
    public static final Field SNAPSHOT_MODE_CLASS;
    public static final Field LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST;
    public static final Field LOGICAL_DECODING_MESSAGE_PREFIX_INCLUDE_LIST;
    /** @deprecated */
    @Deprecated public static final Field TRUNCATE_HANDLING_MODE;

    public static final Field HSTORE_HANDLING_MODE;
    public static final Field INTERVAL_HANDLING_MODE;
    public static final Field STATUS_UPDATE_INTERVAL_MS;
    public static final Field TCP_KEEPALIVE;
    public static final Field INCLUDE_UNKNOWN_DATATYPES;
    public static final Field SCHEMA_REFRESH_MODE;
    public static final Field XMIN_FETCH_INTERVAL;
    /** @deprecated */
    @Deprecated public static final Field TOASTED_VALUE_PLACEHOLDER;

    public static final Field UNAVAILABLE_VALUE_PLACEHOLDER;
    public static final Field MONEY_FRACTION_DIGITS;
    public static final Field SKIPPED_OPERATIONS;
    private final TruncateHandlingMode truncateHandlingMode;
    private final LogicalDecodingMessageFilter logicalDecodingMessageFilter;
    private final HStoreHandlingMode hStoreHandlingMode;
    private final IntervalHandlingMode intervalHandlingMode;
    private final SnapshotMode snapshotMode;
    private final SchemaRefreshMode schemaRefreshMode;
    private static final ConfigDefinition CONFIG_DEFINITION;
    public static Field.Set ALL_FIELDS;

    public PostgresConnectorConfig(Configuration config) {
        super(
                config,
                config.getString(RelationalDatabaseConnectorConfig.SERVER_NAME),
                new SystemTablesPredicate(),
                (x) -> {
                    return x.schema() + "." + x.table();
                },
                10240,
                ColumnFilterMode.SCHEMA);
        this.truncateHandlingMode =
                PostgresConnectorConfig.TruncateHandlingMode.parse(
                        config.getString(TRUNCATE_HANDLING_MODE));
        this.logicalDecodingMessageFilter =
                new LogicalDecodingMessageFilter(
                        config.getString(LOGICAL_DECODING_MESSAGE_PREFIX_INCLUDE_LIST),
                        config.getString(LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST));
        String hstoreHandlingModeStr = config.getString(HSTORE_HANDLING_MODE);
        this.hStoreHandlingMode =
                PostgresConnectorConfig.HStoreHandlingMode.parse(hstoreHandlingModeStr);
        this.intervalHandlingMode =
                PostgresConnectorConfig.IntervalHandlingMode.parse(
                        config.getString(INTERVAL_HANDLING_MODE));
        this.snapshotMode =
                PostgresConnectorConfig.SnapshotMode.parse(config.getString(SNAPSHOT_MODE));
        this.schemaRefreshMode =
                PostgresConnectorConfig.SchemaRefreshMode.parse(
                        config.getString(SCHEMA_REFRESH_MODE));
    }

    protected String hostname() {
        return this.getConfig().getString(HOSTNAME);
    }

    protected int port() {
        return this.getConfig().getInteger(PORT);
    }

    public String databaseName() {
        return this.getConfig().getString(DATABASE_NAME);
    }

    protected LogicalDecoder plugin() {
        return PostgresConnectorConfig.LogicalDecoder.parse(
                this.getConfig().getString(PLUGIN_NAME));
    }

    protected String slotName() {
        return this.getConfig().getString(SLOT_NAME);
    }

    protected boolean dropSlotOnStop() {
        return this.getConfig().hasKey(DROP_SLOT_ON_STOP.name())
                ? this.getConfig().getBoolean(DROP_SLOT_ON_STOP)
                : this.getConfig().getBoolean(DROP_SLOT_ON_STOP);
    }

    public String publicationName() {
        return this.getConfig().getString(PUBLICATION_NAME);
    }

    public EnumSet<Envelope.Operation> getSkippedOperations() {
        EnumSet<Envelope.Operation> skippedOperations = super.getSkippedOperations();
        if (PostgresConnectorConfig.TruncateHandlingMode.SKIP.equals(this.truncateHandlingMode)) {
            skippedOperations.add(Operation.TRUNCATE);
        }

        return skippedOperations;
    }

    protected AutoCreateMode publicationAutocreateMode() {
        return PostgresConnectorConfig.AutoCreateMode.parse(
                this.getConfig().getString(PUBLICATION_AUTOCREATE_MODE));
    }

    protected String streamParams() {
        return this.getConfig().getString(STREAM_PARAMS);
    }

    public int maxRetries() {
        return this.getConfig().getInteger(MAX_RETRIES);
    }

    public Duration retryDelay() {
        return Duration.ofMillis((long) this.getConfig().getInteger(RETRY_DELAY_MS));
    }

    protected Duration statusUpdateInterval() {
        return Duration.ofMillis(this.getConfig().getLong(STATUS_UPDATE_INTERVAL_MS));
    }

    public LogicalDecodingMessageFilter getMessageFilter() {
        return this.logicalDecodingMessageFilter;
    }

    protected HStoreHandlingMode hStoreHandlingMode() {
        return this.hStoreHandlingMode;
    }

    protected IntervalHandlingMode intervalHandlingMode() {
        return this.intervalHandlingMode;
    }

    protected boolean includeUnknownDatatypes() {
        return this.getConfig().getBoolean(INCLUDE_UNKNOWN_DATATYPES);
    }

    public Map<String, ConfigValue> validate() {
        return this.getConfig().validate(ALL_FIELDS);
    }

    protected Snapshotter getSnapshotter() {
        return this.snapshotMode.getSnapshotter(this.getConfig());
    }

    protected boolean skipRefreshSchemaOnMissingToastableData() {
        return PostgresConnectorConfig.SchemaRefreshMode.COLUMNS_DIFF_EXCLUDE_UNCHANGED_TOAST
                == this.schemaRefreshMode;
    }

    protected Duration xminFetchInterval() {
        return Duration.ofMillis(this.getConfig().getLong(XMIN_FETCH_INTERVAL));
    }

    public byte[] getUnavailableValuePlaceholder() {
        String placeholder = this.getConfig().getString(TOASTED_VALUE_PLACEHOLDER);
        if (Strings.isNullOrEmpty(placeholder)) {
            placeholder = this.getConfig().getString(UNAVAILABLE_VALUE_PLACEHOLDER);
        }

        return placeholder.startsWith("hex:")
                ? Strings.hexStringToByteArray(placeholder.substring(4))
                : placeholder.getBytes();
    }

    protected int moneyFractionDigits() {
        return this.getConfig().getInteger(MONEY_FRACTION_DIGITS);
    }

    protected SourceInfoStructMaker<? extends AbstractSourceInfo> getSourceInfoStructMaker(
            CommonConnectorConfig.Version version) {
        switch (version) {
            case V1:
                return new LegacyV1PostgresSourceInfoStructMaker(
                        Module.name(), Module.version(), this);
            default:
                return new PostgresSourceInfoStructMaker(Module.name(), Module.version(), this);
        }
    }

    public static ConfigDef configDef() {
        return CONFIG_DEFINITION.configDef();
    }

    private static int validateReplicationSlotName(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String name = config.getString(field);
        int errors = 0;
        if (name != null && !name.matches("[a-z0-9_]{1,63}")) {
            problems.accept(
                    field,
                    name,
                    "Valid replication slot name must contain only digits, lowercase characters and underscores with length <= 63");
            ++errors;
        }

        return errors;
    }

    private static int validateTruncateHandlingMode(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String value = config.getString(field);
        int errors = 0;
        if (value != null) {
            TruncateHandlingMode truncateHandlingMode =
                    PostgresConnectorConfig.TruncateHandlingMode.parse(value);
            String message;
            if (truncateHandlingMode == null) {
                List<String> validModes =
                        Arrays.stream(PostgresConnectorConfig.TruncateHandlingMode.values())
                                .map(TruncateHandlingMode::getValue)
                                .collect(Collectors.toList());
                message =
                        String.format(
                                "Valid values for %s are %s, but got '%s'",
                                field.name(), validModes, value);
                problems.accept(field, value, message);
                ++errors;
                return errors;
            }

            if (truncateHandlingMode == PostgresConnectorConfig.TruncateHandlingMode.INCLUDE) {
                LogicalDecoder logicalDecoder =
                        PostgresConnectorConfig.LogicalDecoder.parse(config.getString(PLUGIN_NAME));
                if (!logicalDecoder.supportsTruncate()) {
                    message =
                            String.format(
                                    "%s '%s' is not supported with configuration %s '%s'",
                                    field.name(),
                                    truncateHandlingMode.getValue(),
                                    PLUGIN_NAME.name(),
                                    logicalDecoder.getValue());
                    problems.accept(field, value, message);
                    ++errors;
                }
            }

            if (errors == 0) {
                LOGGER.warn(
                        "Configuration property '{}' is deprecated and will be removed in future versions. Please use '{}' instead.",
                        TRUNCATE_HANDLING_MODE.name(),
                        SKIPPED_OPERATIONS.name());
            }
        }

        return errors;
    }

    private static int validateSkippedOperations(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String value = config.getString(field.name(), (String) null);
        boolean isTruncateSkipped = false;
        if (value != null) {
            String[] operations = value.split(",");
            String[] var6 = operations;
            int var7 = operations.length;

            for (int var8 = 0; var8 < var7; ++var8) {
                String operation = var6[var8];
                if ("t".equals(operation)) {
                    isTruncateSkipped = true;
                    break;
                }
            }
        }

        if (!isTruncateSkipped) {
            LogicalDecoder logicalDecoder =
                    PostgresConnectorConfig.LogicalDecoder.parse(config.getString(PLUGIN_NAME));
            if (!logicalDecoder.supportsTruncate()) {
                return 0;
            }

            TruncateHandlingMode truncateHandlingMode =
                    PostgresConnectorConfig.TruncateHandlingMode.parse(
                            config.getString(TRUNCATE_HANDLING_MODE));
            if (truncateHandlingMode == PostgresConnectorConfig.TruncateHandlingMode.SKIP) {
                LOGGER.warn(
                        "Configuration property '{}' is deprecated and will be removed soon. If you wish to retain skipped truncate functionality, please configure '{}' with \"{}\".",
                        new Object[] {
                            TRUNCATE_HANDLING_MODE.name(), SKIPPED_OPERATIONS.name(), "t"
                        });
            }
        }

        return 0;
    }

    private static int validateToastedValuePlaceholder(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String placeholder = config.getString(TOASTED_VALUE_PLACEHOLDER);
        if (!Strings.isNullOrEmpty(placeholder)) {
            LOGGER.warn(
                    "Configuration property '{}' is deprecated and will be removed in future versions. Please use '{}' instead.",
                    TOASTED_VALUE_PLACEHOLDER.name(),
                    UNAVAILABLE_VALUE_PLACEHOLDER.name());
        }

        return 0;
    }

    private static int validatePluginName(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String pluginName = config.getString(PLUGIN_NAME);
        if (!Strings.isNullOrEmpty(pluginName) && pluginName.startsWith("wal2json")) {
            LOGGER.warn(
                    "Logical decoder '{}' is deprecated and will be removed in future versions",
                    pluginName);
        }

        return 0;
    }

    private static int validateLogicalDecodingMessageExcludeList(
            Configuration config, Field field, Field.ValidationOutput problems) {
        String includeList = config.getString(LOGICAL_DECODING_MESSAGE_PREFIX_INCLUDE_LIST);
        String excludeList = config.getString(LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST);
        if (includeList != null && excludeList != null) {
            problems.accept(
                    LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST,
                    excludeList,
                    "\"logical_decoding_message.prefix.include.list\" is already specified");
            return 1;
        } else {
            return 0;
        }
    }

    public String getContextName() {
        return Module.contextName();
    }

    public String getConnectorName() {
        return Module.name();
    }

    static {
        PORT = RelationalDatabaseConnectorConfig.PORT.withDefault(8001);
        PLUGIN_NAME =
                Field.create("plugin.name")
                        .withDisplayName("Plugin")
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 0))
                        .withEnum(
                                LogicalDecoder.class,
                                PostgresConnectorConfig.LogicalDecoder.DECODERBUFS)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {PostgresConnectorConfig::validatePluginName})
                        .withDescription(
                                "The name of the Postgres logical decoding plugin installed on the server. Supported values are '"
                                        + PostgresConnectorConfig.LogicalDecoder.DECODERBUFS
                                                .getValue()
                                        + "' and '"
                                        + PostgresConnectorConfig.LogicalDecoder.PGOUTPUT.getValue()
                                        + "'. Defaults to '"
                                        + PostgresConnectorConfig.LogicalDecoder.DECODERBUFS
                                                .getValue()
                                        + "'.");
        SLOT_NAME =
                Field.create("slot.name")
                        .withDisplayName("Slot")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 1))
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withDefault("debezium")
                        .withValidation(
                                new Field.Validator[] {
                                    PostgresConnectorConfig::validateReplicationSlotName
                                })
                        .withDescription(
                                "The name of the Postgres logical decoding slot created for streaming changes from a plugin.Defaults to 'debezium");
        DROP_SLOT_ON_STOP =
                Field.create("slot.drop.on.stop")
                        .withDisplayName("Drop slot on stop")
                        .withType(Type.BOOLEAN)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 3))
                        .withDefault(false)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Whether or not to drop the logical replication slot when the connector finishes orderlyBy default the replication is kept so that on restart progress can resume from the last recorded location");
        PUBLICATION_NAME =
                Field.create("publication.name")
                        .withDisplayName("Publication")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 8))
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withDefault("dbz_publication")
                        .withDescription(
                                "The name of the Postgres 10+ publication used for streaming changes from a plugin.Defaults to 'dbz_publication'");
        PUBLICATION_AUTOCREATE_MODE =
                Field.create("publication.autocreate.mode")
                        .withDisplayName("Publication Auto Create Mode")
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 9))
                        .withEnum(
                                AutoCreateMode.class,
                                PostgresConnectorConfig.AutoCreateMode.ALL_TABLES)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Applies only when streaming changes using pgoutput.Determine how creation of a publication should work, the default is all_tables.DISABLED - The connector will not attempt to create a publication at all. The expectation is that the user has created the publication up-front. If the publication isn't found to exist upon startup, the connector will throw an exception and stop.ALL_TABLES - If no publication exists, the connector will create a new publication for all tables. Note this requires that the configured user has access. If the publication already exists, it will be used. i.e CREATE PUBLICATION <publication_name> FOR ALL TABLES;FILTERED - If no publication exists, the connector will create a new publication for all those tables matchingthe current filter configuration (see table/database include/exclude list properties). If the publication already exists, it will be used. i.e CREATE PUBLICATION <publication_name> FOR TABLE <tbl1, tbl2, etc>");
        STREAM_PARAMS =
                Field.create("slot.stream.params")
                        .withDisplayName(
                                "Optional parameters to pass to the logical decoder when the stream is started.")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 2))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.LOW)
                        .withDescription(
                                "Any optional parameters used by logical decoding plugin. Semi-colon separated. E.g. 'add-tables=public.table,public.table2;include-lsn=true'");
        MAX_RETRIES =
                Field.create("slot.max.retries")
                        .withDisplayName("Retry count")
                        .withType(Type.INT)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 4))
                        .withImportance(Importance.LOW)
                        .withDefault(6)
                        .withValidation(new Field.Validator[] {Field::isInteger})
                        .withDescription(
                                "How many times to retry connecting to a replication slot when an attempt fails.");
        RETRY_DELAY_MS =
                Field.create("slot.retry.delay.ms")
                        .withDisplayName("Retry delay")
                        .withType(Type.LONG)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 5))
                        .withImportance(Importance.LOW)
                        .withDefault(Duration.ofSeconds(10L).toMillis())
                        .withValidation(new Field.Validator[] {Field::isInteger})
                        .withDescription(
                                "Time to wait between retry attempts when the connector fails to connect to a replication slot, given in milliseconds. Defaults to 10 seconds (10,000 ms).");
        ON_CONNECT_STATEMENTS =
                Field.create("database." + JdbcConfiguration.ON_CONNECT_STATEMENTS)
                        .withDisplayName("Initial statements")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED, 1))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.LOW)
                        .withDescription(
                                "A semicolon separated list of SQL statements to be executed when a JDBC connection to the database is established. Note that the connector may establish JDBC connections at its own discretion, so this should typically be used for configurationof session parameters only, but not for executing DML statements. Use doubled semicolon (';;') to use a semicolon as a character and not as a delimiter.");
        SSL_MODE =
                Field.create("database.sslmode")
                        .withDisplayName("SSL mode")
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 0))
                        .withEnum(
                                SecureConnectionMode.class,
                                PostgresConnectorConfig.SecureConnectionMode.DISABLED)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Whether to use an encrypted connection to Postgres. Options include'disable' (the default) to use an unencrypted connection; 'require' to use a secure (encrypted) connection, and fail if one cannot be established; 'verify-ca' like 'required' but additionally verify the server TLS certificate against the configured Certificate Authority (CA) certificates, or fail if no valid matching CA certificates are found; or'verify-full' like 'verify-ca' but additionally verify that the server certificate matches the host to which the connection is attempted.");
        SSL_CLIENT_CERT =
                Field.create("database.sslcert")
                        .withDisplayName("SSL Client Certificate")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 1))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "File containing the SSL Certificate for the client. See the Postgres SSL docs for further information");
        SSL_CLIENT_KEY =
                Field.create("database.sslkey")
                        .withDisplayName("SSL Client Key")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 4))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "File containing the SSL private key for the client. See the Postgres SSL docs for further information");
        SSL_CLIENT_KEY_PASSWORD =
                Field.create("database.sslpassword")
                        .withDisplayName("SSL Client Key Password")
                        .withType(Type.PASSWORD)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 2))
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Password to access the client private key from the file specified by 'database.sslkey'. See the Postgres SSL docs for further information");
        SSL_ROOT_CERT =
                Field.create("database.sslrootcert")
                        .withDisplayName("SSL Root Certificate")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 3))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "File containing the root certificate(s) against which the server is validated. See the Postgres JDBC SSL docs for further information");
        SSL_SOCKET_FACTORY =
                Field.create("database.sslfactory")
                        .withDisplayName("SSL Root Certificate")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_SSL, 5))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "A name of class to that creates SSL Sockets. Use org.postgresql.ssl.NonValidatingFactory to disable SSL validation in development environments");
        SNAPSHOT_MODE =
                Field.create("snapshot.mode")
                        .withDisplayName("Snapshot mode")
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR_SNAPSHOT, 0))
                        .withEnum(SnapshotMode.class, PostgresConnectorConfig.SnapshotMode.INITIAL)
                        .withWidth(Width.SHORT)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {
                                    (config, field, output) -> {
                                        if (config.getString(field)
                                                .toLowerCase()
                                                .equals(
                                                        PostgresConnectorConfig.SnapshotMode
                                                                .EXPORTED
                                                                .getValue())) {
                                            LOGGER.warn(
                                                    "Value '{}' of 'snapshot.mode' option is deprecated, use '{}' instead",
                                                    PostgresConnectorConfig.SnapshotMode.EXPORTED
                                                            .getValue(),
                                                    PostgresConnectorConfig.SnapshotMode.INITIAL
                                                            .getValue());
                                        }

                                        return 0;
                                    }
                                })
                        .withDescription(
                                "The criteria for running a snapshot upon startup of the connector. Options include: 'always' to specify that the connector run a snapshot each time it starts up; 'initial' (the default) to specify the connector can run a snapshot only when no offsets are available for the logical server name; 'initial_only' same as 'initial' except the connector should stop after completing the snapshot and before it would normally start emitting changes;'never' to specify the connector should never run a snapshot and that upon first startup the connector should read from the last position (LSN) recorded by the server; and'exported' deprecated, use 'initial' instead; 'custom' to specify a custom class with 'snapshot.custom_class' which will be loaded and used to determine the snapshot, see docs for more details.");
        SNAPSHOT_MODE_CLASS =
                Field.create("snapshot.custom.class")
                        .withDisplayName("Snapshot Mode Custom Class")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR_SNAPSHOT, 9))
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {
                                    (config, field, output) -> {
                                        if (config.getString(SNAPSHOT_MODE)
                                                        .toLowerCase()
                                                        .equals("custom")
                                                && config.getString(field).isEmpty()) {
                                            output.accept(
                                                    field,
                                                    "",
                                                    "snapshot.custom_class cannot be empty when snapshot.mode 'custom' is defined");
                                            return 1;
                                        } else {
                                            return 0;
                                        }
                                    }
                                })
                        .withDescription(
                                "When 'snapshot.mode' is set as custom, this setting must be set to specify a fully qualified class name to load (via the default class loader).This class must implement the 'Snapshotter' interface and is called on each app boot to determine whether to do a snapshot and how to build queries.");
        LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST =
                Field.create("message.prefix.exclude.list")
                        .withDisplayName("Exclude Logical Decoding Message Prefixes")
                        .withType(Type.LIST)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 25))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {
                                    Field::isListOfRegex,
                                    PostgresConnectorConfig
                                            ::validateLogicalDecodingMessageExcludeList
                                })
                        .withDescription(
                                "A comma-separated list of regular expressions that match the logical decoding message prefixes to be excluded from monitoring.");
        LOGICAL_DECODING_MESSAGE_PREFIX_INCLUDE_LIST =
                Field.create("message.prefix.include.list")
                        .withDisplayName("Include Logical Decoding Message Prefixes")
                        .withType(Type.LIST)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 24))
                        .withWidth(Width.LONG)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(new Field.Validator[] {Field::isListOfRegex})
                        .withDescription(
                                "A comma-separated list of regular expressions that match the logical decoding message prefixes to be monitored. All prefixes are monitored by default.");
        TRUNCATE_HANDLING_MODE =
                Field.create("truncate.handling.mode")
                        .withDisplayName("Truncate handling mode")
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 23))
                        .withEnum(
                                TruncateHandlingMode.class,
                                PostgresConnectorConfig.TruncateHandlingMode.SKIP)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {
                                    PostgresConnectorConfig::validateTruncateHandlingMode
                                })
                        .withDescription(
                                "(Deprecated) Specify how TRUNCATE operations are handled for change events (supported only on pg11+ pgoutput plugin), including: 'skip' to skip / ignore TRUNCATE events (default), 'include' to handle and include TRUNCATE events. Use 'skipped.operations' instead.");
        HSTORE_HANDLING_MODE =
                Field.create("hstore.handling.mode")
                        .withDisplayName("HStore Handling")
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 22))
                        .withEnum(
                                HStoreHandlingMode.class,
                                PostgresConnectorConfig.HStoreHandlingMode.JSON)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.LOW)
                        .withDescription(
                                "Specify how HSTORE columns should be represented in change events, including:'json' represents values as string-ified JSON (default)'map' represents values as a key/value map");
        INTERVAL_HANDLING_MODE =
                Field.create("interval.handling.mode")
                        .withDisplayName("Interval Handling")
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 21))
                        .withEnum(
                                IntervalHandlingMode.class,
                                PostgresConnectorConfig.IntervalHandlingMode.NUMERIC)
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.LOW)
                        .withDescription(
                                "Specify how INTERVAL columns should be represented in change events, including:'string' represents values as an exact ISO formatted string'numeric' (default) represents values using the inexact conversion into microseconds");
        STATUS_UPDATE_INTERVAL_MS =
                Field.create("status.update.interval.ms")
                        .withDisplayName("Status update interval (ms)")
                        .withType(Type.INT)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 6))
                        .withDefault(10000)
                        .withWidth(Width.SHORT)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Frequency for sending replication connection status updates to the server, given in milliseconds. Defaults to 10 seconds (10,000 ms).")
                        .withValidation(new Field.Validator[] {Field::isPositiveInteger});
        TCP_KEEPALIVE =
                Field.create("database.tcpKeepAlive")
                        .withDisplayName("TCP keep-alive probe")
                        .withType(Type.BOOLEAN)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED, 0))
                        .withDefault(true)
                        .withWidth(Width.SHORT)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Enable or disable TCP keep-alive probe to avoid dropping TCP connection")
                        .withValidation(new Field.Validator[] {Field::isBoolean});
        INCLUDE_UNKNOWN_DATATYPES =
                Field.create("include.unknown.datatypes")
                        .withDisplayName("Include unknown datatypes")
                        .withType(Type.BOOLEAN)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR_ADVANCED, 1))
                        .withDefault(false)
                        .withWidth(Width.SHORT)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Specify whether the fields of data type not supported by Debezium should be processed:'false' (the default) omits the fields; 'true' converts the field into an implementation dependent binary representation.");
        SCHEMA_REFRESH_MODE =
                Field.create("schema.refresh.mode")
                        .withDisplayName("Schema refresh mode")
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR_ADVANCED, 0))
                        .withEnum(
                                SchemaRefreshMode.class,
                                PostgresConnectorConfig.SchemaRefreshMode.COLUMNS_DIFF)
                        .withWidth(Width.SHORT)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Specify the conditions that trigger a refresh of the in-memory schema for a table. 'columns_diff' (the default) is the safest mode, ensuring the in-memory schema stays in-sync with the database table's schema at all times. 'columns_diff_exclude_unchanged_toast' instructs the connector to refresh the in-memory schema cache if there is a discrepancy between it and the schema derived from the incoming message, unless unchanged TOASTable data fully accounts for the discrepancy. This setting can improve connector performance significantly if there are frequently-updated tables that have TOASTed data that are rarely part of these updates. However, it is possible for the in-memory schema to become outdated if TOASTable columns are dropped from the table.");
        XMIN_FETCH_INTERVAL =
                Field.create("xmin.fetch.interval.ms")
                        .withDisplayName("Xmin fetch interval (ms)")
                        .withType(Type.LONG)
                        .withGroup(Field.createGroupEntry(Group.CONNECTION_ADVANCED_REPLICATION, 7))
                        .withWidth(Width.SHORT)
                        .withDefault(0L)
                        .withImportance(Importance.MEDIUM)
                        .withDescription(
                                "Specify how often (in ms) the xmin will be fetched from the replication slot. This xmin value is exposed by the slot which gives a lower bound of where a new replication slot could start from. The lower the value, the more likely this value is to be the current 'true' value, but the bigger the performance cost. The bigger the value, the less likely this value is to be the current 'true' value, but the lower the performance penalty. The default is set to 0 ms, which disables tracking xmin.")
                        .withValidation(new Field.Validator[] {Field::isNonNegativeLong});
        TOASTED_VALUE_PLACEHOLDER =
                Field.create("toasted.value.placeholder")
                        .withDisplayName("Toasted value placeholder")
                        .withType(Type.STRING)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR_ADVANCED, 2))
                        .withWidth(Width.MEDIUM)
                        .withDefault("__debezium_unavailable_value")
                        .withImportance(Importance.MEDIUM)
                        .withValidation(
                                new Field.Validator[] {
                                    PostgresConnectorConfig::validateToastedValuePlaceholder
                                })
                        .withDescription(
                                "Specify the constant that will be provided by Debezium to indicate that the original value is a toasted value not provided by the database. If starts with 'hex:' prefix it is expected that the rest of the string represents hexadecimal encoded octets.Deprecated, use 'unavailable.value.placeholder' instead.");
        UNAVAILABLE_VALUE_PLACEHOLDER =
                RelationalDatabaseConnectorConfig.UNAVAILABLE_VALUE_PLACEHOLDER.withDescription(
                        "Specify the constant that will be provided by Debezium to indicate that the original value is a toasted value not provided by the database. If starts with 'hex:' prefix it is expected that the rest of the string represents hexadecimal encoded octets.");
        MONEY_FRACTION_DIGITS =
                Field.create("money.fraction.digits")
                        .withDisplayName("Money fraction digits")
                        .withType(Type.SHORT)
                        .withGroup(Field.createGroupEntry(Group.CONNECTOR, 1))
                        .withWidth(Width.MEDIUM)
                        .withImportance(Importance.LOW)
                        .withDefault(2)
                        .withDescription(
                                "Number of fractional digits when money type is converted to 'precise' decimal number.");
        SKIPPED_OPERATIONS =
                CommonConnectorConfig.SKIPPED_OPERATIONS
                        .withDefault("t")
                        .withValidation(
                                new Field.Validator[] {
                                    (x$0, x$1, x$2) -> {
                                        return CommonConnectorConfig.validateSkippedOperation(
                                                x$0, x$1, x$2);
                                    },
                                    PostgresConnectorConfig::validateSkippedOperations
                                });
        CONFIG_DEFINITION =
                RelationalDatabaseConnectorConfig.CONFIG_DEFINITION
                        .edit()
                        .name("GaussDB")
                        .excluding(new Field[] {CommonConnectorConfig.SKIPPED_OPERATIONS})
                        .type(
                                new Field[] {
                                    HOSTNAME,
                                    PORT,
                                    USER,
                                    PASSWORD,
                                    DATABASE_NAME,
                                    PLUGIN_NAME,
                                    SLOT_NAME,
                                    PUBLICATION_NAME,
                                    PUBLICATION_AUTOCREATE_MODE,
                                    DROP_SLOT_ON_STOP,
                                    STREAM_PARAMS,
                                    ON_CONNECT_STATEMENTS,
                                    SSL_MODE,
                                    SSL_CLIENT_CERT,
                                    SSL_CLIENT_KEY_PASSWORD,
                                    SSL_ROOT_CERT,
                                    SSL_CLIENT_KEY,
                                    MAX_RETRIES,
                                    RETRY_DELAY_MS,
                                    SSL_SOCKET_FACTORY,
                                    STATUS_UPDATE_INTERVAL_MS,
                                    TCP_KEEPALIVE,
                                    XMIN_FETCH_INTERVAL,
                                    SKIPPED_OPERATIONS
                                })
                        .events(new Field[] {INCLUDE_UNKNOWN_DATATYPES, TOASTED_VALUE_PLACEHOLDER})
                        .connector(
                                new Field[] {
                                    SNAPSHOT_MODE,
                                    SNAPSHOT_MODE_CLASS,
                                    HSTORE_HANDLING_MODE,
                                    BINARY_HANDLING_MODE,
                                    SCHEMA_NAME_ADJUSTMENT_MODE,
                                    INTERVAL_HANDLING_MODE,
                                    SCHEMA_REFRESH_MODE,
                                    TRUNCATE_HANDLING_MODE,
                                    INCREMENTAL_SNAPSHOT_CHUNK_SIZE,
                                    UNAVAILABLE_VALUE_PLACEHOLDER,
                                    LOGICAL_DECODING_MESSAGE_PREFIX_INCLUDE_LIST,
                                    LOGICAL_DECODING_MESSAGE_PREFIX_EXCLUDE_LIST
                                })
                        .excluding(new Field[] {INCLUDE_SCHEMA_CHANGES})
                        .create();
        ALL_FIELDS = Field.setOf(CONFIG_DEFINITION.all());
    }

    private static class SystemTablesPredicate implements Tables.TableFilter {
        protected static final List<String> SYSTEM_SCHEMAS =
                Arrays.asList("pg_catalog", "information_schema");
        protected static final List<String> SYSTEM_TABLES = Arrays.asList("spatial_ref_sys");
        protected static final String TEMP_TABLE_SCHEMA_PREFIX = "pg_temp";

        private SystemTablesPredicate() {}

        public boolean isIncluded(TableId t) {
            return !SYSTEM_SCHEMAS.contains(t.schema().toLowerCase())
                    && !SYSTEM_TABLES.contains(t.table().toLowerCase())
                    && !t.schema().startsWith("pg_temp");
        }
    }

    /** @deprecated */
    @Deprecated
    public static enum TruncateHandlingMode implements EnumeratedValue {
        SKIP("skip"),
        INCLUDE("include");

        private final String value;

        private TruncateHandlingMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static TruncateHandlingMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                TruncateHandlingMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    TruncateHandlingMode truncateHandlingMode = var1[var3];
                    if (truncateHandlingMode.getValue().equalsIgnoreCase(value)) {
                        return truncateHandlingMode;
                    }
                }

                return null;
            }
        }
    }

    public static enum HStoreHandlingMode implements EnumeratedValue {
        JSON("json"),
        MAP("map");

        private final String value;

        private HStoreHandlingMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static HStoreHandlingMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                HStoreHandlingMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    HStoreHandlingMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }

        public static HStoreHandlingMode parse(String value, String defaultValue) {
            HStoreHandlingMode mode = parse(value);
            if (mode == null && defaultValue != null) {
                mode = parse(defaultValue);
            }

            return mode;
        }
    }

    public static enum IntervalHandlingMode implements EnumeratedValue {
        NUMERIC("numeric"),
        STRING("string");

        private final String value;

        private IntervalHandlingMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static IntervalHandlingMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                IntervalHandlingMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    IntervalHandlingMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }

        public static IntervalHandlingMode parse(String value, String defaultValue) {
            IntervalHandlingMode mode = parse(value);
            if (mode == null && defaultValue != null) {
                mode = parse(defaultValue);
            }

            return mode;
        }
    }

    public static enum SnapshotMode implements EnumeratedValue {
        ALWAYS(
                "always",
                (c) -> {
                    return new AlwaysSnapshotter();
                }),
        INITIAL(
                "initial",
                (c) -> {
                    return new InitialSnapshotter();
                }),
        NEVER(
                "never",
                (c) -> {
                    return new NeverSnapshotter();
                }),
        INITIAL_ONLY(
                "initial_only",
                (c) -> {
                    return new InitialOnlySnapshotter();
                }),
        /** @deprecated */
        @Deprecated
        EXPORTED(
                "exported",
                (c) -> {
                    return new InitialSnapshotter();
                }),
        CUSTOM(
                "custom",
                (c) -> {
                    return (Snapshotter)
                            c.getInstance(
                                    PostgresConnectorConfig.SNAPSHOT_MODE_CLASS, Snapshotter.class);
                });

        private final String value;
        private final SnapshotterBuilder builderFunc;

        private SnapshotMode(String value, SnapshotterBuilder buildSnapshotter) {
            this.value = value;
            this.builderFunc = buildSnapshotter;
        }

        public Snapshotter getSnapshotter(Configuration config) {
            return this.builderFunc.buildSnapshotter(config);
        }

        public String getValue() {
            return this.value;
        }

        public static SnapshotMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                SnapshotMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    SnapshotMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }

        public static SnapshotMode parse(String value, String defaultValue) {
            SnapshotMode mode = parse(value);
            if (mode == null && defaultValue != null) {
                mode = parse(defaultValue);
            }

            return mode;
        }

        @FunctionalInterface
        public interface SnapshotterBuilder {
            Snapshotter buildSnapshotter(Configuration var1);
        }
    }

    public static enum SchemaRefreshMode implements EnumeratedValue {
        COLUMNS_DIFF("columns_diff"),
        COLUMNS_DIFF_EXCLUDE_UNCHANGED_TOAST("columns_diff_exclude_unchanged_toast");

        private final String value;

        private SchemaRefreshMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static SchemaRefreshMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                SchemaRefreshMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    SchemaRefreshMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }
    }

    public static enum LogicalDecoder implements EnumeratedValue {
        PGOUTPUT("pgoutput") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new PgOutputMessageDecoder(config, connection);
            }

            public String getPostgresPluginName() {
                return this.getValue();
            }

            public boolean supportsTruncate() {
                return true;
            }

            public boolean supportsLogicalDecodingMessage() {
                return true;
            }
        },
        DECODERBUFS("decoderbufs") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new PgProtoMessageDecoder();
            }

            public String getPostgresPluginName() {
                return this.getValue();
            }

            public boolean supportsTruncate() {
                return false;
            }

            public boolean supportsLogicalDecodingMessage() {
                return false;
            }
        },
        /** @deprecated */
        @Deprecated
        WAL2JSON_STREAMING("wal2json_streaming") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new StreamingWal2JsonMessageDecoder();
            }

            public String getPostgresPluginName() {
                return "wal2json";
            }

            public boolean supportsTruncate() {
                return false;
            }

            public boolean hasUnchangedToastColumnMarker() {
                return false;
            }

            public boolean sendsNullToastedValuesInOld() {
                return false;
            }

            public boolean supportsLogicalDecodingMessage() {
                return false;
            }
        },
        /** @deprecated */
        @Deprecated
        WAL2JSON_RDS_STREAMING("wal2json_rds_streaming") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new StreamingWal2JsonMessageDecoder();
            }

            public boolean forceRds() {
                return true;
            }

            public String getPostgresPluginName() {
                return "wal2json";
            }

            public boolean supportsTruncate() {
                return false;
            }

            public boolean hasUnchangedToastColumnMarker() {
                return false;
            }

            public boolean sendsNullToastedValuesInOld() {
                return false;
            }

            public boolean supportsLogicalDecodingMessage() {
                return false;
            }
        },
        /** @deprecated */
        @Deprecated
        WAL2JSON("wal2json") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new NonStreamingWal2JsonMessageDecoder();
            }

            public String getPostgresPluginName() {
                return "wal2json";
            }

            public boolean supportsTruncate() {
                return false;
            }

            public boolean hasUnchangedToastColumnMarker() {
                return false;
            }

            public boolean sendsNullToastedValuesInOld() {
                return false;
            }

            public boolean supportsLogicalDecodingMessage() {
                return true;
            }
        },
        /** @deprecated */
        @Deprecated
        WAL2JSON_RDS("wal2json_rds") {
            public MessageDecoder messageDecoder(
                    MessageDecoderContext config, GaussDBConnection connection) {
                return new NonStreamingWal2JsonMessageDecoder();
            }

            public boolean forceRds() {
                return true;
            }

            public String getPostgresPluginName() {
                return "wal2json";
            }

            public boolean supportsTruncate() {
                return false;
            }

            public boolean hasUnchangedToastColumnMarker() {
                return false;
            }

            public boolean sendsNullToastedValuesInOld() {
                return false;
            }

            public boolean supportsLogicalDecodingMessage() {
                return false;
            }
        };

        private final String decoderName;

        private LogicalDecoder(String decoderName) {
            this.decoderName = decoderName;
        }

        public abstract MessageDecoder messageDecoder(
                MessageDecoderContext var1, GaussDBConnection var2);

        public boolean forceRds() {
            return false;
        }

        public boolean hasUnchangedToastColumnMarker() {
            return true;
        }

        public boolean sendsNullToastedValuesInOld() {
            return true;
        }

        public static LogicalDecoder parse(String s) {
            return valueOf(s.trim().toUpperCase());
        }

        public String getValue() {
            return this.decoderName;
        }

        public abstract String getPostgresPluginName();

        public abstract boolean supportsTruncate();

        public abstract boolean supportsLogicalDecodingMessage();
    }

    public static enum AutoCreateMode implements EnumeratedValue {
        DISABLED("disabled"),
        ALL_TABLES("all_tables"),
        FILTERED("filtered");

        private final String value;

        private AutoCreateMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static AutoCreateMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                AutoCreateMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    AutoCreateMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }

        public static AutoCreateMode parse(String value, String defaultValue) {
            AutoCreateMode mode = parse(value);
            if (mode == null && defaultValue != null) {
                mode = parse(defaultValue);
            }

            return mode;
        }
    }

    public static enum SecureConnectionMode implements EnumeratedValue {
        DISABLED("disable"),
        REQUIRED("require"),
        VERIFY_CA("verify-ca"),
        VERIFY_FULL("verify-full");

        private final String value;

        private SecureConnectionMode(String value) {
            this.value = value;
        }

        public String getValue() {
            return this.value;
        }

        public static SecureConnectionMode parse(String value) {
            if (value == null) {
                return null;
            } else {
                value = value.trim();
                SecureConnectionMode[] var1 = values();
                int var2 = var1.length;

                for (int var3 = 0; var3 < var2; ++var3) {
                    SecureConnectionMode option = var1[var3];
                    if (option.getValue().equalsIgnoreCase(value)) {
                        return option;
                    }
                }

                return null;
            }
        }

        public static SecureConnectionMode parse(String value, String defaultValue) {
            SecureConnectionMode mode = parse(value);
            if (mode == null && defaultValue != null) {
                mode = parse(defaultValue);
            }

            return mode;
        }
    }
}
